Skip to content

Replace ComponentSignals with IOPorts implementation#481

Draft
PhilipFackler wants to merge 1 commit into
developfrom
PhilipFackler/signal-ports
Draft

Replace ComponentSignals with IOPorts implementation#481
PhilipFackler wants to merge 1 commit into
developfrom
PhilipFackler/signal-ports

Conversation

@PhilipFackler

Copy link
Copy Markdown
Collaborator

Description

This is a refactor of ComponentSignals to be (hopefully) more intuitive and also make model development a bit easier.

Proposed changes

Manage signal node connections via input and output ports

  • Introduce a set of classes that represent a collection of input and output ports that manage connection to signal nodes
  • Make input/output ports directly available to components
  • Clear up terminology: distinguish between signal node and signal. Use "connect" rather than "assign" and "attach" (keep "assign" as implementation detail) and "link" rather than "set"
  • Introduce SignalNodeSet class for managing the signal nodes for the system. This can then be passed to components since it doesn't require access to the SystemModel implementation. This maps either signal_id or name to signal nodes.
  • Have IOPorts automatically connect signal nodes to ports at construction (rather than doing it manually in SystemModel
  • Remove ComponentSignals

Checklist

  • All tests pass.
  • Code compiles cleanly with flags -Wall -Wpedantic -Wconversion -Wextra.
  • The new code follows GridKit™ style guidelines.
  • There are unit tests for the new code.
  • The new code is documented.
  • The feature branch is rebased with respect to the target branch.
  • I have updated CHANGELOG.md to reflect the changes in this PR. If this is a minor PR that is part of a larger fix already included in the file, state so.

@PhilipFackler PhilipFackler requested review from lukelowry, nkoukpaizan and pelesh and removed request for lukelowry July 9, 2026 16:12
@lukelowry

Copy link
Copy Markdown
Collaborator

This looks awesome!!

@lukelowry lukelowry left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So so helpful for my development. This is very intuitive and helps understand and debug intermodel behavior. I would like this to be merged before the new Phasor Dynamics models so I can use this syntax!

Masterful this helps so much with modeling

@lukelowry

Copy link
Copy Markdown
Collaborator

As a related question, for future development in EMT: how difficult would this be to implement for vector-typed signals instead of scalar-typed? In an ideal world, the best possible design for EMT is a N width signal (decided at runtime), which I have no clue how to implement.

@PhilipFackler

Copy link
Copy Markdown
Collaborator Author

As a related question, for future development in EMT: how difficult would this be to implement for vector-typed signals instead of scalar-typed? In an ideal world, the best possible design for EMT is a N width signal (decided at runtime), which I have no clue how to implement.

Handling the variable should be pretty simple. Especially if it can be wrapped in a non-owning vector. That's what readSignal would return. Otherwise it would work exactly the same way.

The more challenging part would be the use of SignalNode. Would we need to re-implement (duplicate) things, or could we generalize?

@pelesh pelesh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is certainly step in the right direction and makes component model signal connections more streamlined.

A few things that need to be addressed before this is ready for review:

  • Component model constructors should take only reference to the component model data as input parameters. All signal connectivity information is already in there.
  • Treat signal input and outputs separately as these are different types of ports (nodes).
  • Implement different classes/structs in separate source files.
  • Align naming of the object with what we have agreed earlier.

Comment on lines +18 to +22
template <typename scalar_type, typename index_type>
class Port
{
public:
/// Scalar type used for signals

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class Port must be in separate source file from IOPorts.

Comment on lines +34 to +35
void connect(SignalNodeT* node) noexcept
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node and port are often used interchangeably in nodal analysis. What is node in this case?

Comment on lines +79 to +81
template <typename scalar_type, typename index_type>
class OutputPort : public Port<scalar_type, index_type>
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we decided to name this SignalOut. This should also be in a separate file.

Comment on lines +109 to +115
/**
* @brief Port for receiving a signal from a SignalNode
*/
template <typename scalar_type, typename index_type>
class InputPort : public Port<scalar_type, index_type>
{
public:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to SignalIn or SignalInput and implement in a separate file.

Comment on lines +237 to +241
template <typename scalar_type, typename model_data_type>
struct IOPorts
{
/// Scalar type used for signals
using ScalarT = scalar_type;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More intuitive name would be SignalPorts. Needs to be implemented in a separate file.

Comment on lines +119 to +120
exciter.getPorts().out[SexsPtiSignalOutputs::efd].connect(&efd_node);
exciter.getPorts().in[SexsPtiSignalInputs::vs].connect(&vs_node);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A better approach would be to have methods getSignalInput() and getSignalOutput()`.

Suggested change
exciter.getPorts().out[SexsPtiSignalOutputs::efd].connect(&efd_node);
exciter.getPorts().in[SexsPtiSignalInputs::vs].connect(&vs_node);
exciter.getSignalOutput[SexsPtiSignalOutputs::efd].connect(&efd_node);
exciter.getSignalInput[SexsPtiSignalInputs::vs].connect(&vs_node);

Similar for other components.

Comment on lines +22 to +31
* A signal node conceptually carries a signal (scalar value) from an
* output port of one component to an input port of another component (or
* multiple components).
*
* (Component):[OutputPort] -> {SignalNode} -> [InputPort]:(Component)
*
* A SignalNode can be "connected" to a Port. When that port is an
* OutputPort, the SignalNode is considered `assigned()` since it can be
* connected to only one OutputPort. The SignalNode is considered `linked()`
* when the actual signal (scalar variable) has been made available.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be some confusion with naming scheme here: The output port is a signal node. What is called a SignalNode here is a signal link.

Comment on lines +102 to 105
IOPortsT& getPorts()
{
return signals_;
return ports_;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would create separate methods for input and output signal ports. These are two different types of objects with different connection properties.

Comment on lines +90 to 91
Ieeest(const ModelDataT& data, SignalNodeSetT& signal_nodes);
~Ieeest();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constructor should not take reference to SignalNodeSetT as the input parameter. All signal connectivity information is already in ModelDataT. Similar for other component models.

Suggested change
Ieeest(const ModelDataT& data, SignalNodeSetT& signal_nodes);
~Ieeest();
~Ieeest();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants